//---------------------------------------------------------------------
//
// QR Code Generator for JavaScript
//
// Copyright (c) 2009 Kazuhiko Arase
//  some additions (c) 2012 Volker Kinkelin
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
//	http://www.opensource.org/licenses/mit-license.php
//	additions by Volker Kinkelin are also under MIT license
//
// The word 'QR Code' is registered trademark of
// DENSO WAVE INCORPORATED
//	http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------

var qrcode = function () {

	//---------------------------------------------------------------------
	// qrcode
	//---------------------------------------------------------------------

	/**
	 * qrcode
	 * @param typeNumber 1 to 10
	 * @param errorCorrectLevel 'L','M','Q','H'
	 */
	var qrcode = function (typeNumber, errorCorrectLevel) {

		var PAD0 = 0xEC;
		var PAD1 = 0x11;

		var _typeNumber = typeNumber;
		var _errorCorrectLevel = QRErrorCorrectLevel[errorCorrectLevel];
		var _modules = null;
		var _moduleCount = 0;
		var _dataCache = null;
		var _dataList = new Array();

		var _this = {};

		var makeImpl = function (test, maskPattern) {

			_moduleCount = _typeNumber * 4 + 17;
			_modules = function (moduleCount) {
				var modules = new Array(moduleCount);
				for (var row = 0; row < moduleCount; row += 1) {
					modules[row] = new Array(moduleCount);
					for (var col = 0; col < moduleCount; col += 1) {
						modules[row][col] = null;
					}
				}
				return modules;
			}
			(_moduleCount);

			setupPositionProbePattern(0, 0);
			setupPositionProbePattern(_moduleCount - 7, 0);
			setupPositionProbePattern(0, _moduleCount - 7);
			setupPositionAdjustPattern();
			setupTimingPattern();
			setupTypeInfo(test, maskPattern);

			if (_typeNumber >= 7) {
				setupTypeNumber(test);
			}

			if (_dataCache == null) {
				_dataCache = createData(_typeNumber, _errorCorrectLevel, _dataList);
			}

			mapData(_dataCache, maskPattern);
		};

		var setupPositionProbePattern = function (row, col) {

			for (var r = -1; r <= 7; r += 1) {

				if (row + r <= -1 || _moduleCount <= row + r)
					continue;

				for (var c = -1; c <= 7; c += 1) {

					if (col + c <= -1 || _moduleCount <= col + c)
						continue;

					if ((0 <= r && r <= 6 && (c == 0 || c == 6))
						 || (0 <= c && c <= 6 && (r == 0 || r == 6))
						 || (2 <= r && r <= 4 && 2 <= c && c <= 4)) {
						_modules[row + r][col + c] = true;
					} else {
						_modules[row + r][col + c] = false;
					}
				}
			}
		};

		var getBestMaskPattern = function () {

			var minLostPoint = 0;
			var pattern = 0;

			for (var i = 0; i < 8; i += 1) {

				makeImpl(true, i);

				var lostPoint = QRUtil.getLostPoint(_this);

				if (i == 0 || minLostPoint > lostPoint) {
					minLostPoint = lostPoint;
					pattern = i;
				}
			}

			return pattern;
		};

		var setupTimingPattern = function () {

			for (var r = 8; r < _moduleCount - 8; r += 1) {
				if (_modules[r][6] != null) {
					continue;
				}
				_modules[r][6] = (r % 2 == 0);
			}

			for (var c = 8; c < _moduleCount - 8; c += 1) {
				if (_modules[6][c] != null) {
					continue;
				}
				_modules[6][c] = (c % 2 == 0);
			}
		};

		var setupPositionAdjustPattern = function () {

			var pos = QRUtil.getPatternPosition(_typeNumber);

			for (var i = 0; i < pos.length; i += 1) {

				for (var j = 0; j < pos.length; j += 1) {

					var row = pos[i];
					var col = pos[j];

					if (_modules[row][col] != null) {
						continue;
					}

					for (var r = -2; r <= 2; r += 1) {

						for (var c = -2; c <= 2; c += 1) {

							if (r == -2 || r == 2 || c == -2 || c == 2
								 || (r == 0 && c == 0)) {
								_modules[row + r][col + c] = true;
							} else {
								_modules[row + r][col + c] = false;
							}
						}
					}
				}
			}
		};

		var setupTypeNumber = function (test) {

			var bits = QRUtil.getBCHTypeNumber(_typeNumber);

			for (var i = 0; i < 18; i += 1) {
				var mod = (!test && ((bits >> i) & 1) == 1);
				_modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
			}

			for (var i = 0; i < 18; i += 1) {
				var mod = (!test && ((bits >> i) & 1) == 1);
				_modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
			}
		};

		var setupTypeInfo = function (test, maskPattern) {

			var data = (_errorCorrectLevel << 3) | maskPattern;
			var bits = QRUtil.getBCHTypeInfo(data);

			// vertical
			for (var i = 0; i < 15; i += 1) {

				var mod = (!test && ((bits >> i) & 1) == 1);

				if (i < 6) {
					_modules[i][8] = mod;
				} else if (i < 8) {
					_modules[i + 1][8] = mod;
				} else {
					_modules[_moduleCount - 15 + i][8] = mod;
				}
			}

			// horizontal
			for (var i = 0; i < 15; i += 1) {

				var mod = (!test && ((bits >> i) & 1) == 1);

				if (i < 8) {
					_modules[8][_moduleCount - i - 1] = mod;
				} else if (i < 9) {
					_modules[8][15 - i - 1 + 1] = mod;
				} else {
					_modules[8][15 - i - 1] = mod;
				}
			}

			// fixed module
			_modules[_moduleCount - 8][8] = (!test);
		};

		var mapData = function (data, maskPattern) {

			var inc = -1;
			var row = _moduleCount - 1;
			var bitIndex = 7;
			var byteIndex = 0;
			var maskFunc = QRUtil.getMaskFunction(maskPattern);

			for (var col = _moduleCount - 1; col > 0; col -= 2) {

				if (col == 6)
					col -= 1;
				while (true) {

					for (var c = 0; c < 2; c += 1) {

						if (_modules[row][col - c] == null) {

							var dark = false;

							if (byteIndex < data.length) {
								dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
							}

							var mask = maskFunc(row, col - c);

							if (mask) {
								dark = !dark;
							}

							_modules[row][col - c] = dark;
							bitIndex -= 1;

							if (bitIndex == -1) {
								byteIndex += 1;
								bitIndex = 7;
							}
						}
					}

					row += inc;

					if (row < 0 || _moduleCount <= row) {
						row -= inc;
						inc = -inc;
						break;
					}
				}
			}
		};

		var createBytes = function (buffer, rsBlocks) {

			var offset = 0;

			var maxDcCount = 0;
			var maxEcCount = 0;

			var dcdata = new Array(rsBlocks.length);
			var ecdata = new Array(rsBlocks.length);

			for (var r = 0; r < rsBlocks.length; r += 1) {

				var dcCount = rsBlocks[r].dataCount;
				var ecCount = rsBlocks[r].totalCount - dcCount;

				maxDcCount = Math.max(maxDcCount, dcCount);
				maxEcCount = Math.max(maxEcCount, ecCount);

				dcdata[r] = new Array(dcCount);

				for (var i = 0; i < dcdata[r].length; i += 1) {
					dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
				}
				offset += dcCount;

				var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
				var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);

				var modPoly = rawPoly.mod(rsPoly);
				ecdata[r] = new Array(rsPoly.getLength() - 1);
				for (var i = 0; i < ecdata[r].length; i += 1) {
					var modIndex = i + modPoly.getLength() - ecdata[r].length;
					ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0;
				}
			}

			var totalCodeCount = 0;
			for (var i = 0; i < rsBlocks.length; i += 1) {
				totalCodeCount += rsBlocks[i].totalCount;
			}

			var data = new Array(totalCodeCount);
			var index = 0;

			for (var i = 0; i < maxDcCount; i += 1) {
				for (var r = 0; r < rsBlocks.length; r += 1) {
					if (i < dcdata[r].length) {
						data[index] = dcdata[r][i];
						index += 1;
					}
				}
			}

			for (var i = 0; i < maxEcCount; i += 1) {
				for (var r = 0; r < rsBlocks.length; r += 1) {
					if (i < ecdata[r].length) {
						data[index] = ecdata[r][i];
						index += 1;
					}
				}
			}

			return data;
		};

		var createData = function (typeNumber, errorCorrectLevel, dataList) {

			var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel);

			var buffer = qrBitBuffer();

			for (var i = 0; i < dataList.length; i += 1) {
				var data = dataList[i];
				buffer.put(data.getMode(), 4);
				buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber));
				data.write(buffer);
			}

			// calc num max data.
			var totalDataCount = 0;
			for (var i = 0; i < rsBlocks.length; i += 1) {
				totalDataCount += rsBlocks[i].dataCount;
			}

			if (buffer.getLengthInBits() > totalDataCount * 8) {
				throw new Error('code length overflow. ('
					 + buffer.getLengthInBits()
					 + '>'
					 + totalDataCount * 8
					 + ')');
			}

			// end code
			if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
				buffer.put(0, 4);
			}

			// padding
			while (buffer.getLengthInBits() % 8 != 0) {
				buffer.putBit(false);
			}

			// padding
			while (true) {

				if (buffer.getLengthInBits() >= totalDataCount * 8) {
					break;
				}
				buffer.put(PAD0, 8);

				if (buffer.getLengthInBits() >= totalDataCount * 8) {
					break;
				}
				buffer.put(PAD1, 8);
			}

			return createBytes(buffer, rsBlocks);
		};

		_this.addData = function (data) {
			var newData = qr8BitByte(data);
			_dataList.push(newData);
			_dataCache = null;
		};

		_this.isDark = function (row, col) {
			if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
				throw new Error(row + ',' + col);
			}
			return _modules[row][col];
		};

		_this.getModuleCount = function () {
			return _moduleCount;
		};

		_this.make = function () {
			makeImpl(false, getBestMaskPattern());
		};

		_this.createTableTag = function (cellSize, margin) {

			cellSize = cellSize || 2;
			margin = (typeof margin == 'undefined') ? cellSize * 4 : margin;

			var qrHtml = '';

			qrHtml += '<table style="';
			qrHtml += ' border-width: 0px; border-style: none;';
			qrHtml += ' border-collapse: collapse;';
			qrHtml += ' padding: 0px; margin: ' + margin + 'px;';
			qrHtml += '">';
			qrHtml += '<tbody>';

			for (var r = 0; r < _this.getModuleCount(); r += 1) {

				qrHtml += '<tr>';

				for (var c = 0; c < _this.getModuleCount(); c += 1) {
					qrHtml += '<td style="';
					qrHtml += ' border-width: 0px; border-style: none;';
					qrHtml += ' border-collapse: collapse;';
					qrHtml += ' padding: 0px; margin: 0px;';
					qrHtml += ' width: ' + cellSize + 'px;';
					qrHtml += ' height: ' + cellSize + 'px;';
					qrHtml += ' background-color: ';
					qrHtml += _this.isDark(r, c) ? '#000000' : '#ffffff';
					qrHtml += ';';
					qrHtml += '"/>';
				}

				qrHtml += '</tr>';
			}

			qrHtml += '</tbody>';
			qrHtml += '</table>';

			return qrHtml;
		};

		_this.createImgTag = function (cellSize, margin) {

			cellSize = cellSize || 2;
			margin = (typeof margin == 'undefined') ? cellSize * 4 : margin;

			var size = _this.getModuleCount() * cellSize + margin * 2;
			var min = margin;
			var max = size - margin;

			return createImgTag(size, size, function (x, y) {
				if (min <= x && x < max && min <= y && y < max) {
					var c = Math.floor((x - min) / cellSize);
					var r = Math.floor((y - min) / cellSize);
					return _this.isDark(r, c) ? 0 : 1;
				} else {
					return 1;
				}
			});
		};

		//---------------------------------------------------------------------
		// ==== additions 05.03.2012 Volker Kinkelin start ====
		//---------------------------------------------------------------------

		_this.getDOMSubTree = function (args) {

			args = args || {};

			// if code is provided, prepare this instance first ('lazy self-instanciation')
			if (args.code) {

				this.addData(args.code);
				this.make();

			}

			// get resolution (always quadratic!)
			var moduleCount = this.getModuleCount();

			// this node will be returned by the method
			var treeRoot; // is the root node for the whole QRcode div subtree

			// root node specified as a string or a DOM node or not at all?
			switch (typeof args.nodeParent) {

			case 'string':
				treeRoot = document.getElementById(args.nodeParent);
				break;

			case 'object':
				treeRoot = args.nodeParent;
				break;

			default:

				// no node specified -> create root node yourself
				// the existing DOM tree is not modified in this case (!)
				// the node must be inserted by the caller later
				treeRoot = document.createElement('div');

				// specify containing DIV's CSS class, 'QRContainer' as default
				var nodeAttribute = document.createAttribute('class');
				nodeAttribute.nodeValue = args.cssClassContainer = args.cssClassContainer || 'QRContainer';

				treeRoot.setAttributeNode(nodeAttribute);

			}

			// rule out placeholder ("'...wait...' etc) text
			treeRoot.innerHTML = '';

			// append DIV's attributes (style, ID, title etc) if specified
			if (args.divContainerAttributes) {

				for (var attribName in args.divContainerAttributes) {

					nodeAttribute = document.createAttribute(attribName);
					nodeAttribute.nodeValue = args.divContainerAttributes[attribName];
					treeRoot.setAttributeNode(nodeAttribute);

				}

			}

			// prepare template divs nodes to be cloned for black & white blocks
			var dotNodeBlack = document.createElement('div');
			var nodeAttributeBlack = document.createAttribute('class');
			nodeAttributeBlack.nodeValue = args.cssClassBlack = args.cssClassBlack || 'QB';
			dotNodeBlack.setAttributeNode(nodeAttributeBlack);

			var dotNodeWhite = document.createElement('div');
			var nodeAttributeWhite = document.createAttribute('class');
			nodeAttributeWhite.nodeValue = args.cssClassWhite = args.cssClassWhite || 'QW';
			dotNodeWhite.setAttributeNode(nodeAttributeWhite);

			// prepare row end (line-break) div node
			var dotNodeRowEnd = document.createElement('div');
			var nodeAttributeRowEnd = document.createAttribute('class');
			nodeAttributeRowEnd.nodeValue = args.cssClassRowEnd = args.cssClassRowEnd || 'QRE';
			dotNodeRowEnd.setAttributeNode(nodeAttributeRowEnd);

			// for each x-y block
			for (var row = 0; row < moduleCount; row++) {

				for (var column = 0; column < moduleCount; column++) {

					// not using _this.isDark() here for performance reasons
					if (_modules[row][column])
						treeRoot.appendChild(dotNodeBlack.cloneNode(false));
					else
						treeRoot.appendChild(dotNodeWhite.cloneNode(false));

				}

				treeRoot.appendChild(dotNodeRowEnd.cloneNode(false));

			}

			return treeRoot;

		}; // _this.getDOMSubTree = function (args) {

		_this.drawOnCanvas = function (args) {

			args = args || {};

			// if code is provided, prepare instance first ('lazy self-instanciation')
			if (args.code) {

				this.addData(args.code);
				this.make();

			}

			// make sure div for canvas is given
			if (!args.divCanvas)
				return -2; // = no canvas div specified

			var domCanvas; // = where to draw on

			// canvas specified as ID [string] or DOM node?
			switch (typeof args.divCanvas) {

			case 'string':
				domCanvas = document.getElementById(args.divCanvas);
				break;

			case 'object':
				domCanvas = args.divCanvas;
				break;

			}

			// make sure canvas exists
			if (!domCanvas || !domCanvas.getContext)
				return -3;

			// if specified, add/update CANVAS' attributes (style, ID, title etc)
			if (args.divContainerAttributes) {

				for (var attribName in args.divContainerAttributes) {

					nodeAttribute = document.createAttribute(attribName);
					nodeAttribute.nodeValue = args.divContainerAttributes[attribName];
					domCanvas.setAttributeNode(nodeAttribute);

				}

			}

			// get number of dots (black squares) in code
			var moduleCount = this.getModuleCount();
			var dotSize; // = dot size in pixels

			// adapt canvas size to dot witdh specified
			if (typeof args.dotSize == 'number') {

				dotSize = args.dotSize;

				domCanvas.setAttribute('width', moduleCount * args.dotSize);
				domCanvas.setAttribute('height', moduleCount * args.dotSize);

			} else // adapt dot width to existing canvans element size
				dotSize = Math.floor(domCanvas.width / moduleCount);

			// get drawing context
			var canvasContext = domCanvas.getContext('2d');

			// define dots fillstyle, usually solid black. background is transparent
			canvasContext.fillStyle = args.dotFillStyle = args.dotFillStyle || 'rgb(0,0,0)';

			// for each block
			for (var row = 0; row < moduleCount; row++) {

				for (var column = 0; column < moduleCount; column++) {

					// not using _this.isDark() here for performance reasons
					if (_modules[row][column])
						canvasContext.fillRect(column * dotSize, row * dotSize, dotSize, dotSize);

				}

			}

			return 0; // = success

		}; // _this.drawOnCanvas = function (args) {

		//---------------------------------------------------------------------
		// ==== additions 05.03.2012 Volker Kinkelin end ====
		//---------------------------------------------------------------------

		return _this;
	};

	//---------------------------------------------------------------------
	// qrcode.stringToBytes
	//---------------------------------------------------------------------

	qrcode.stringToBytes = function (s) {
		var bytes = new Array();
		for (var i = 0; i < s.length; i += 1) {
			var c = s.charCodeAt(i);
			bytes.push(c & 0xff);
		}
		return bytes;
	};

	//---------------------------------------------------------------------
	// qrcode.createStringToBytes
	//---------------------------------------------------------------------

	/**
	 * @param unicodeData base64 string of byte array.
	 * [16bit Unicode],[16bit Bytes], ...
	 * @param numChars
	 */
	qrcode.createStringToBytes = function (unicodeData, numChars) {

		// create conversion map.

		var unicodeMap = function () {

			var bin = base64DecodeInputStream(unicodeData);
			var read = function () {
				var b = bin.read();
				if (b == -1)
					throw new Error();
				return b;
			};

			var count = 0;
			var unicodeMap = {};
			while (true) {
				var b0 = bin.read();
				if (b0 == -1)
					break;
				var b1 = read();
				var b2 = read();
				var b3 = read();
				var k = String.fromCharCode((b0 << 8) | b1);
				var v = (b2 << 8) | b3;
				unicodeMap[k] = v;
				count += 1;
			}
			if (count != numChars) {
				throw new Error(count + ' != ' + numChars);
			}

			return unicodeMap;
		}
		();

		var unknownChar = '?'.charCodeAt(0);

		return function (s) {
			var bytes = new Array();
			for (var i = 0; i < s.length; i += 1) {
				var c = s.charCodeAt(i);
				if (c < 128) {
					bytes.push(c);
				} else {
					var b = unicodeMap[s.charAt(i)];
					if (typeof b == 'number') {
						if ((b & 0xff) == b) {
							// 1byte
							bytes.push(b);
						} else {
							// 2bytes
							bytes.push(b >>> 8);
							bytes.push(b & 0xff);
						}
					} else {
						bytes.push(unknownChar);
					}
				}
			}
			return bytes;
		};
	};

	//---------------------------------------------------------------------
	// QRMode
	//---------------------------------------------------------------------

	var QRMode = {
		MODE_NUMBER : 1 << 0,
		MODE_ALPHA_NUM : 1 << 1,
		MODE_8BIT_BYTE : 1 << 2,
		MODE_KANJI : 1 << 3
	};

	//---------------------------------------------------------------------
	// QRErrorCorrectLevel
	//---------------------------------------------------------------------

	var QRErrorCorrectLevel = {
		L : 1,
		M : 0,
		Q : 3,
		H : 2
	};

	//---------------------------------------------------------------------
	// QRMaskPattern
	//---------------------------------------------------------------------

	var QRMaskPattern = {
		PATTERN000 : 0,
		PATTERN001 : 1,
		PATTERN010 : 2,
		PATTERN011 : 3,
		PATTERN100 : 4,
		PATTERN101 : 5,
		PATTERN110 : 6,
		PATTERN111 : 7
	};

	//---------------------------------------------------------------------
	// QRUtil
	//---------------------------------------------------------------------

	var QRUtil = function () {

		var PATTERN_POSITION_TABLE = [
			[],
			[6, 18],
			[6, 22],
			[6, 26],
			[6, 30],
			[6, 34],
			[6, 22, 38],
			[6, 24, 42],
			[6, 26, 46],
			[6, 28, 50],
			[6, 30, 54],
			[6, 32, 58],
			[6, 34, 62],
			[6, 26, 46, 66],
			[6, 26, 48, 70],
			[6, 26, 50, 74],
			[6, 30, 54, 78],
			[6, 30, 56, 82],
			[6, 30, 58, 86],
			[6, 34, 62, 90],
			[6, 28, 50, 72, 94],
			[6, 26, 50, 74, 98],
			[6, 30, 54, 78, 102],
			[6, 28, 54, 80, 106],
			[6, 32, 58, 84, 110],
			[6, 30, 58, 86, 114],
			[6, 34, 62, 90, 118],
			[6, 26, 50, 74, 98, 122],
			[6, 30, 54, 78, 102, 126],
			[6, 26, 52, 78, 104, 130],
			[6, 30, 56, 82, 108, 134],
			[6, 34, 60, 86, 112, 138],
			[6, 30, 58, 86, 114, 142],
			[6, 34, 62, 90, 118, 146],
			[6, 30, 54, 78, 102, 126, 150],
			[6, 24, 50, 76, 102, 128, 154],
			[6, 28, 54, 80, 106, 132, 158],
			[6, 32, 58, 84, 110, 136, 162],
			[6, 26, 54, 82, 110, 138, 166],
			[6, 30, 58, 86, 114, 142, 170]
		];
		var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
		var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
		var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);

		var _this = {};

		var getBCHDigit = function (data) {
			var digit = 0;
			while (data != 0) {
				digit += 1;
				data >>>= 1;
			}
			return digit;
		};

		_this.getBCHTypeInfo = function (data) {
			var d = data << 10;
			while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
				d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15)));
			}
			return ((data << 10) | d)^G15_MASK;
		};

		_this.getBCHTypeNumber = function (data) {
			var d = data << 12;
			while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
				d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18)));
			}
			return (data << 12) | d;
		};

		_this.getPatternPosition = function (typeNumber) {
			return PATTERN_POSITION_TABLE[typeNumber - 1];
		};

		_this.getMaskFunction = function (maskPattern) {

			switch (maskPattern) {

			case QRMaskPattern.PATTERN000:
				return function (i, j) {
					return (i + j) % 2 == 0;
				};
			case QRMaskPattern.PATTERN001:
				return function (i, j) {
					return i % 2 == 0;
				};
			case QRMaskPattern.PATTERN010:
				return function (i, j) {
					return j % 3 == 0;
				};
			case QRMaskPattern.PATTERN011:
				return function (i, j) {
					return (i + j) % 3 == 0;
				};
			case QRMaskPattern.PATTERN100:
				return function (i, j) {
					return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
				};
			case QRMaskPattern.PATTERN101:
				return function (i, j) {
					return (i * j) % 2 + (i * j) % 3 == 0;
				};
			case QRMaskPattern.PATTERN110:
				return function (i, j) {
					return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
				};
			case QRMaskPattern.PATTERN111:
				return function (i, j) {
					return ((i * j) % 3 + (i + j) % 2) % 2 == 0;
				};

			default:
				throw new Error('bad maskPattern:' + maskPattern);
			}
		};

		_this.getErrorCorrectPolynomial = function (errorCorrectLength) {
			var a = qrPolynomial([1], 0);
			for (var i = 0; i < errorCorrectLength; i += 1) {
				a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0));
			}
			return a;
		};

		_this.getLengthInBits = function (mode, type) {

			if (1 <= type && type < 10) {

				// 1 - 9

				switch (mode) {
				case QRMode.MODE_NUMBER:
					return 10;
				case QRMode.MODE_ALPHA_NUM:
					return 9;
				case QRMode.MODE_8BIT_BYTE:
					return 8;
				case QRMode.MODE_KANJI:
					return 8;
				default:
					throw new Error('mode:' + mode);
				}

			} else if (type < 27) {

				// 10 - 26

				switch (mode) {
				case QRMode.MODE_NUMBER:
					return 12;
				case QRMode.MODE_ALPHA_NUM:
					return 11;
				case QRMode.MODE_8BIT_BYTE:
					return 16;
				case QRMode.MODE_KANJI:
					return 10;
				default:
					throw new Error('mode:' + mode);
				}

			} else if (type < 41) {

				// 27 - 40

				switch (mode) {
				case QRMode.MODE_NUMBER:
					return 14;
				case QRMode.MODE_ALPHA_NUM:
					return 13;
				case QRMode.MODE_8BIT_BYTE:
					return 16;
				case QRMode.MODE_KANJI:
					return 12;
				default:
					throw new Error('mode:' + mode);
				}

			} else {
				throw new Error('type:' + type);
			}
		};

		_this.getLostPoint = function (qrcode) {

			var moduleCount = qrcode.getModuleCount();

			var lostPoint = 0;

			// LEVEL1

			for (var row = 0; row < moduleCount; row += 1) {
				for (var col = 0; col < moduleCount; col += 1) {

					var sameCount = 0;
					var dark = qrcode.isDark(row, col);

					for (var r = -1; r <= 1; r += 1) {

						if (row + r < 0 || moduleCount <= row + r) {
							continue;
						}

						for (var c = -1; c <= 1; c += 1) {

							if (col + c < 0 || moduleCount <= col + c) {
								continue;
							}

							if (r == 0 && c == 0) {
								continue;
							}

							if (dark == qrcode.isDark(row + r, col + c)) {
								sameCount += 1;
							}
						}
					}

					if (sameCount > 5) {
						lostPoint += (3 + sameCount - 5);
					}
				}
			};

			// LEVEL2

			for (var row = 0; row < moduleCount - 1; row += 1) {
				for (var col = 0; col < moduleCount - 1; col += 1) {
					var count = 0;
					if (qrcode.isDark(row, col))
						count += 1;
					if (qrcode.isDark(row + 1, col))
						count += 1;
					if (qrcode.isDark(row, col + 1))
						count += 1;
					if (qrcode.isDark(row + 1, col + 1))
						count += 1;
					if (count == 0 || count == 4) {
						lostPoint += 3;
					}
				}
			}

			// LEVEL3

			for (var row = 0; row < moduleCount; row += 1) {
				for (var col = 0; col < moduleCount - 6; col += 1) {
					if (qrcode.isDark(row, col)
						 && !qrcode.isDark(row, col + 1)
						 && qrcode.isDark(row, col + 2)
						 && qrcode.isDark(row, col + 3)
						 && qrcode.isDark(row, col + 4)
						 && !qrcode.isDark(row, col + 5)
						 && qrcode.isDark(row, col + 6)) {
						lostPoint += 40;
					}
				}
			}

			for (var col = 0; col < moduleCount; col += 1) {
				for (var row = 0; row < moduleCount - 6; row += 1) {
					if (qrcode.isDark(row, col)
						 && !qrcode.isDark(row + 1, col)
						 && qrcode.isDark(row + 2, col)
						 && qrcode.isDark(row + 3, col)
						 && qrcode.isDark(row + 4, col)
						 && !qrcode.isDark(row + 5, col)
						 && qrcode.isDark(row + 6, col)) {
						lostPoint += 40;
					}
				}
			}

			// LEVEL4

			var darkCount = 0;

			for (var col = 0; col < moduleCount; col += 1) {
				for (var row = 0; row < moduleCount; row += 1) {
					if (qrcode.isDark(row, col)) {
						darkCount += 1;
					}
				}
			}

			var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
			lostPoint += ratio * 10;

			return lostPoint;
		};

		return _this;
	}
	();

	//---------------------------------------------------------------------
	// QRMath
	//---------------------------------------------------------------------

	var QRMath = function () {

		var EXP_TABLE = new Array(256);
		var LOG_TABLE = new Array(256);

		// initialize tables
		for (var i = 0; i < 8; i += 1) {
			EXP_TABLE[i] = 1 << i;
		}
		for (var i = 8; i < 256; i += 1) {
			EXP_TABLE[i] = EXP_TABLE[i - 4]
				^EXP_TABLE[i - 5]
				^EXP_TABLE[i - 6]
				^EXP_TABLE[i - 8];
		}
		for (var i = 0; i < 255; i += 1) {
			LOG_TABLE[EXP_TABLE[i]] = i;
		}

		var _this = {};

		_this.glog = function (n) {

			if (n < 1) {
				throw new Error('glog(' + n + ')');
			}

			return LOG_TABLE[n];
		};

		_this.gexp = function (n) {
			while (n < 0) {
				n += 255;
			}
			while (n >= 256) {
				n -= 255;
			}

			return EXP_TABLE[n];
		};

		return _this;
	}
	();

	//---------------------------------------------------------------------
	// qrPolynomial
	//---------------------------------------------------------------------

	function qrPolynomial(num, shift) {

		if (typeof num.length == 'undefined') {
			throw new Error(num.length + '/' + shift);
		}

		var _num = function () {
			var offset = 0;
			while (offset < num.length && num[offset] == 0) {
				offset += 1;
			}
			var _num = new Array(num.length - offset + shift);
			for (var i = 0; i < num.length - offset; i += 1) {
				_num[i] = num[i + offset];
			}
			return _num;
		}
		();

		var _this = {};

		_this.get = function (index) {
			return _num[index];
		};

		_this.getLength = function () {
			return _num.length;
		};

		_this.multiply = function (e) {

			var num = new Array(_this.getLength() + e.getLength() - 1);

			for (var i = 0; i < _this.getLength(); i += 1) {
				for (var j = 0; j < e.getLength(); j += 1) {
					num[i + j] ^= QRMath.gexp(QRMath.glog(_this.get(i)) + QRMath.glog(e.get(j)));
				}
			}

			return qrPolynomial(num, 0);
		};

		_this.mod = function (e) {

			if (_this.getLength() - e.getLength() < 0) {
				return _this;
			}

			var ratio = QRMath.glog(_this.get(0)) - QRMath.glog(e.get(0));

			var num = new Array(_this.getLength());
			for (var i = 0; i < _this.getLength(); i += 1) {
				num[i] = _this.get(i);
			}

			for (var i = 0; i < e.getLength(); i += 1) {
				num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio);
			}

			// recursive call
			return qrPolynomial(num, 0).mod(e);
		};

		return _this;
	};

	//---------------------------------------------------------------------
	// QRRSBlock
	//---------------------------------------------------------------------

	var QRRSBlock = function () {

		var RS_BLOCK_TABLE = [

			// L
			// M
			// Q
			// H

			// 1
			[1, 26, 19],
			[1, 26, 16],
			[1, 26, 13],
			[1, 26, 9],

			// 2
			[1, 44, 34],
			[1, 44, 28],
			[1, 44, 22],
			[1, 44, 16],

			// 3
			[1, 70, 55],
			[1, 70, 44],
			[2, 35, 17],
			[2, 35, 13],

			// 4
			[1, 100, 80],
			[2, 50, 32],
			[2, 50, 24],
			[4, 25, 9],

			// 5
			[1, 134, 108],
			[2, 67, 43],
			[2, 33, 15, 2, 34, 16],
			[2, 33, 11, 2, 34, 12],

			// 6
			[2, 86, 68],
			[4, 43, 27],
			[4, 43, 19],
			[4, 43, 15],

			// 7
			[2, 98, 78],
			[4, 49, 31],
			[2, 32, 14, 4, 33, 15],
			[4, 39, 13, 1, 40, 14],

			// 8
			[2, 121, 97],
			[2, 60, 38, 2, 61, 39],
			[4, 40, 18, 2, 41, 19],
			[4, 40, 14, 2, 41, 15],

			// 9
			[2, 146, 116],
			[3, 58, 36, 2, 59, 37],
			[4, 36, 16, 4, 37, 17],
			[4, 36, 12, 4, 37, 13],

			// 10
			[2, 86, 68, 2, 87, 69],
			[4, 69, 43, 1, 70, 44],
			[6, 43, 19, 2, 44, 20],
			[6, 43, 15, 2, 44, 16]
		];

		var qrRSBlock = function (totalCount, dataCount) {
			var _this = {};
			_this.totalCount = totalCount;
			_this.dataCount = dataCount;
			return _this;
		};

		var _this = {};

		var getRsBlockTable = function (typeNumber, errorCorrectLevel) {

			switch (errorCorrectLevel) {
			case QRErrorCorrectLevel.L:
				return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
			case QRErrorCorrectLevel.M:
				return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
			case QRErrorCorrectLevel.Q:
				return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
			case QRErrorCorrectLevel.H:
				return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
			default:
				return undefined;
			}
		};

		_this.getRSBlocks = function (typeNumber, errorCorrectLevel) {

			var rsBlock = getRsBlockTable(typeNumber, errorCorrectLevel);

			if (typeof rsBlock == 'undefined') {
				throw new Error('bad rs block @ typeNumber:' + typeNumber +
					'/errorCorrectLevel:' + errorCorrectLevel);
			}

			var length = rsBlock.length / 3;

			var list = new Array();

			for (var i = 0; i < length; i += 1) {

				var count = rsBlock[i * 3 + 0];
				var totalCount = rsBlock[i * 3 + 1];
				var dataCount = rsBlock[i * 3 + 2];

				for (var j = 0; j < count; j += 1) {
					list.push(qrRSBlock(totalCount, dataCount));
				}
			}

			return list;
		};

		return _this;
	}
	();

	//---------------------------------------------------------------------
	// qrBitBuffer
	//---------------------------------------------------------------------

	var qrBitBuffer = function () {

		var _buffer = new Array();
		var _length = 0;

		var _this = {};

		_this.getBuffer = function () {
			return _buffer;
		};

		_this.get = function (index) {
			var bufIndex = Math.floor(index / 8);
			return ((_buffer[bufIndex] >>> (7 - index % 8)) & 1) == 1;
		};

		_this.put = function (num, length) {
			for (var i = 0; i < length; i += 1) {
				_this.putBit(((num >>> (length - i - 1)) & 1) == 1);
			}
		};

		_this.getLengthInBits = function () {
			return _length;
		};

		_this.putBit = function (bit) {

			var bufIndex = Math.floor(_length / 8);
			if (_buffer.length <= bufIndex) {
				_buffer.push(0);
			}

			if (bit) {
				_buffer[bufIndex] |= (0x80 >>> (_length % 8));
			}

			_length += 1;
		};

		return _this;
	};

	//---------------------------------------------------------------------
	// qr8BitByte
	//---------------------------------------------------------------------

	var qr8BitByte = function (data) {

		var _mode = QRMode.MODE_8BIT_BYTE;
		var _data = data;
		var _bytes = qrcode.stringToBytes(data);

		var _this = {};

		_this.getMode = function () {
			return _mode;
		};

		_this.getLength = function (buffer) {
			return _bytes.length;
		};

		_this.write = function (buffer) {
			for (var i = 0; i < _bytes.length; i += 1) {
				buffer.put(_bytes[i], 8);
			}
		};

		return _this;
	};

	//=====================================================================
	// GIF Support etc.
	//

	//---------------------------------------------------------------------
	// byteArrayOutputStream
	//---------------------------------------------------------------------

	var byteArrayOutputStream = function () {

		var _bytes = new Array();

		var _this = {};

		_this.writeByte = function (b) {
			_bytes.push(b & 0xff);
		};

		_this.writeShort = function (i) {
			_this.writeByte(i);
			_this.writeByte(i >>> 8);
		};

		_this.writeBytes = function (b, off, len) {
			off = off || 0;
			len = len || b.length;
			for (var i = 0; i < len; i += 1) {
				_this.writeByte(b[i + off]);
			}
		};

		_this.writeString = function (s) {
			for (var i = 0; i < s.length; i += 1) {
				_this.writeByte(s.charCodeAt(i));
			}
		};

		_this.toByteArray = function () {
			return _bytes;
		};

		_this.toString = function () {
			var s = '';
			s += '[';
			for (var i = 0; i < _bytes.length; i += 1) {
				if (i > 0) {
					s += ',';
				}
				s += _bytes[i];
			}
			s += ']';
			return s;
		};

		return _this;
	};

	//---------------------------------------------------------------------
	// base64EncodeOutputStream
	//---------------------------------------------------------------------

	var base64EncodeOutputStream = function () {

		var _buffer = 0;
		var _buflen = 0;
		var _length = 0;
		var _base64 = '';

		var _this = {};

		var writeEncoded = function (b) {
			_base64 += String.fromCharCode(encode(b & 0x3f));
		};

		var encode = function (n) {
			if (n < 0) {
				// error.
			} else if (n < 26) {
				return 0x41 + n;
			} else if (n < 52) {
				return 0x61 + (n - 26);
			} else if (n < 62) {
				return 0x30 + (n - 52);
			} else if (n == 62) {
				return 0x2b;
			} else if (n == 63) {
				return 0x2f;
			}
			throw new Error('n:' + n);
		};

		_this.writeByte = function (n) {

			_buffer = (_buffer << 8) | (n & 0xff);
			_buflen += 8;
			_length += 1;
			while (_buflen >= 6) {
				writeEncoded(_buffer >>> (_buflen - 6));
				_buflen -= 6;
			}
		};

		_this.flush = function () {

			if (_buflen > 0) {
				writeEncoded(_buffer << (6 - _buflen));
				_buffer = 0;
				_buflen = 0;
			}

			if (_length % 3 != 0) {
				// padding
				var padlen = 3 - _length % 3;
				for (var i = 0; i < padlen; i += 1) {
					_base64 += '=';
				}
			}
		};

		_this.toString = function () {
			return _base64;
		};

		return _this;
	};

	//---------------------------------------------------------------------
	// base64DecodeInputStream
	//---------------------------------------------------------------------

	var base64DecodeInputStream = function (str) {

		var _str = str;
		var _pos = 0;
		var _buffer = 0;
		var _buflen = 0;

		var _this = {};

		_this.read = function () {
			while (_buflen < 8) {

				if (_pos >= _str.length) {
					if (_buflen == 0) {
						return -1;
					}
					throw new Error('unexpected end of file./' + _buflen);
				}

				var c = _str.charAt(_pos);
				_pos += 1;

				if (c == '=') {
					_buflen = 0;
					return -1;
				} else if (c.match(/^\s$/)) {
					// ignore if whitespace.
					continue;
				}

				_buffer = (_buffer << 6) | decode(c.charCodeAt(0));
				_buflen += 6;
			}

			var n = (_buffer >>> (_buflen - 8)) & 0xff;
			_buflen -= 8;
			return n;
		};

		var decode = function (c) {
			if (0x41 <= c && c <= 0x5a) {
				return c - 0x41;
			} else if (0x61 <= c && c <= 0x7a) {
				return c - 0x61 + 26;
			} else if (0x30 <= c && c <= 0x39) {
				return c - 0x30 + 52;
			} else if (c == 0x2b) {
				return 62;
			} else if (c == 0x2f) {
				return 63;
			} else {
				throw new Error('c:' + c);
			}
		};

		return _this;
	};

	//---------------------------------------------------------------------
	// gifImage (B/W)
	//---------------------------------------------------------------------

	var gifImage = function (width, height) {

		var _width = width;
		var _height = height;
		var _data = new Array(width * height);

		var _this = {};

		_this.setPixel = function (x, y, pixel) {
			_data[y * _width + x] = pixel;
		};

		_this.write = function (out) {

			//---------------------------------
			// GIF Signature

			out.writeString('GIF87a');

			//---------------------------------
			// Screen Descriptor

			out.writeShort(_width);
			out.writeShort(_height);

			out.writeByte(0x80); // 2bit
			out.writeByte(0);
			out.writeByte(0);

			//---------------------------------
			// Global Color Map

			// black
			out.writeByte(0x00);
			out.writeByte(0x00);
			out.writeByte(0x00);

			// white
			out.writeByte(0xff);
			out.writeByte(0xff);
			out.writeByte(0xff);

			//---------------------------------
			// Image Descriptor

			out.writeString(',');
			out.writeShort(0);
			out.writeShort(0);
			out.writeShort(_width);
			out.writeShort(_height);
			out.writeByte(0);

			//---------------------------------
			// Local Color Map

			//---------------------------------
			// Raster Data

			var lzwMinCodeSize = 2;
			var raster = getLZWRaster(lzwMinCodeSize);

			out.writeByte(lzwMinCodeSize);

			var offset = 0;
			while (raster.length - offset > 255) {
				out.writeByte(255);
				out.writeBytes(raster, offset, 255);
				offset += 255;
			}

			out.writeByte(raster.length - offset);
			out.writeBytes(raster, offset, raster.length - offset);
			out.writeByte(0x00);

			//---------------------------------
			// GIF Terminator
			out.writeString(';');
		};

		var bitOutputStream = function (out) {

			var _out = out;
			var _bitLength = 0;
			var _bitBuffer = 0;

			var _this = {};

			_this.write = function (data, length) {

				if ((data >>> length) != 0) {
					throw new Error('length over');
				}
				while (_bitLength + length >= 8) {
					_out.writeByte(0xff & ((data << _bitLength) | _bitBuffer));
					length -= (8 - _bitLength);
					data >>>= (8 - _bitLength);
					_bitBuffer = 0;
					_bitLength = 0;
				}

				_bitBuffer = (data << _bitLength) | _bitBuffer;
				_bitLength = _bitLength + length;
			};

			_this.flush = function () {
				if (_bitLength > 0) {
					_out.writeByte(_bitBuffer);
				}
			};

			return _this;
		};

		var getLZWRaster = function (lzwMinCodeSize) {

			var clearCode = 1 << lzwMinCodeSize;
			var endCode = (1 << lzwMinCodeSize) + 1;
			var bitLength = lzwMinCodeSize + 1;

			// Setup LZWTable
			var table = lzwTable();

			for (var i = 0; i < clearCode; i += 1) {
				table.add(String.fromCharCode(i));
			}
			table.add(String.fromCharCode(clearCode));
			table.add(String.fromCharCode(endCode));

			var byteOut = byteArrayOutputStream();
			var bitOut = bitOutputStream(byteOut);

			// clear code
			bitOut.write(clearCode, bitLength);

			var dataIndex = 0;

			var s = String.fromCharCode(_data[dataIndex]);
			dataIndex += 1;
			while (dataIndex < _data.length) {

				var c = String.fromCharCode(_data[dataIndex]);
				dataIndex += 1;

				if (table.contains(s + c)) {

					s = s + c;

				} else {

					bitOut.write(table.indexOf(s), bitLength);

					if (table.size() < 0xfff) {

						if (table.size() == (1 << bitLength)) {
							bitLength += 1;
						}

						table.add(s + c);
					}

					s = c;
				}
			}

			bitOut.write(table.indexOf(s), bitLength);

			// end code
			bitOut.write(endCode, bitLength);

			bitOut.flush();

			return byteOut.toByteArray();
		};

		var lzwTable = function () {

			var _map = {};
			var _size = 0;

			var _this = {};

			_this.add = function (key) {
				if (_this.contains(key)) {
					throw new Error('dup key:' + key);
				}
				_map[key] = _size;
				_size += 1;
			};

			_this.size = function () {
				return _size;
			};

			_this.indexOf = function (key) {
				return _map[key];
			};

			_this.contains = function (key) {
				return typeof _map[key] != 'undefined';
			};

			return _this;
		};

		return _this;
	};

	var createImgTag = function (width, height, getPixel, alt) {

		var gif = gifImage(width, height);
		for (var y = 0; y < height; y += 1) {
			for (var x = 0; x < width; x += 1) {
				gif.setPixel(x, y, getPixel(x, y));
			}
		}

		var b = byteArrayOutputStream();
		gif.write(b);

		var base64 = base64EncodeOutputStream();
		var bytes = b.toByteArray();
		for (var i = 0; i < bytes.length; i += 1) {
			base64.writeByte(bytes[i]);
		}
		base64.flush();

		var img = '';
		img += '<img';
		img += '\u0020src="';
		img += 'data:image/gif;base64,';
		img += base64;
		img += '"';
		img += '\u0020width="';
		img += width;
		img += '"';
		img += '\u0020height="';
		img += height;
		img += '"';
		if (alt) {
			img += '\u0020alt="';
			img += alt;
			img += '"';
		}
		img += '/>';

		return img;
	};

	//---------------------------------------------------------------------
	// returns qrcode function.

	return qrcode;
}
();

// ==========================================
// ExtJS user extension interface
// (c) 2012 by Volker Kinkelin
//  V1.1 June 27 2012
//  see http://code.google.com/p/qrext/
// Licensed under the MIT license:
//   http://www.opensource.org/licenses/mit-license.php
// ==========================================

Ext.define('Ext.ux.QrPanel', {

	extend : 'Ext.panel.Panel',
	alias : 'widget.qrpanel',

	initComponent : function () {

		var me = this;

		me.callParent(arguments);

		// establish reasonable default parameters
		Ext.applyIf(me, {

			qrBackgroundColor : '#000', // CSS color for background. black
			qrForegroundColor : '#fff', // CSS color for foreground. white = max contrast
			qrNoClickHandler : false, // true, if clicks shouldnt be handeled by qrpanel (click = start fullscreen mode)
			qrDisplayTextWhenFullscreen : true, // display textToEncode when in full screen mode
			qrBlocksize : 4, // width [pixels] of an individual block
			qrRenderMethod : (Ext.supports.Canvas ? 'canvas' : 'divs'), // prefer canvas rendering, if supported
			qrCssTempl : '.QRContainer {display:inline-block} #{3} .QB, #{3} .QW {background-color:{0}; width:{1}; height:{1}; float:left;} #{3} .QW {background-color:{2};} .QRE {clear:both}',

			typeNumber : -1, // defines the number of blocks in the code. Number = typeNumber * 4 + 17 blocks.
			// If set too small, the library throws an error and displays nothing (watch JS console).

			qrErrorCorrectLevel : 'L', // defines the redundancy in the code. One of L, M, Q, H = most redundant

			textToEncode : 'qrext' // ...should be overwritten by caller

		});

		// no Type Number given, find one yourself
		if (me.typeNumber == -1)
			me.typeNumber = me.getMinTypeNumber();

		// still no type number, use 10
		if (me.typeNumber == -1)
			me.typeNumber = 10;

		me.qrModuleCount = this.typeNumber * 4 + 17; // = no of blocks required

		me.width = me.qrBlocksize * me.qrModuleCount; // required panel width in pixels
		me.height = me.width; // QRs are always qadratic

		// use relative size when rendering into DIVs
		if (me.qrRenderMethod == 'divs')
			me.qrBlocksize = (100.0 / me.qrModuleCount) + '%'; // specify width in percent

		me.bodyStyle = 'border:none;'; // no frills

	}, // initComponent : function () {

	afterRender : function () {

		var me = this;

		me.callParent(arguments);

		// adapt panel overall height in case a header is defined
		if (me.header) {

			// wait until header box size is set
			// this event is new in Ext 4.1
			me.header.on('boxready', function () {

				// add panel header height to overall height
				me.setHeight(me.height + me.header.getHeight());

			}, me);

		}

		// what render method is chosen?
		switch (me.qrRenderMethod) {

		case 'divs':
			//
			// render QR as a series of HTML DIVs:
			//

			// add CSS for the blocks, if not present
			if (Ext.util.CSS.getRule('#' + me.id + ' .QB', true) == undefined) {

				// setup CSS rule...
				var temp = Ext.String.format(me.qrCssTempl, me.qrBackgroundColor, me.qrBlocksize, me.qrForegroundColor, me.id);
				// ...save ID for later reference...
				me.qrCssId = me.id + 'QrCss';
				// ...and add it to the document
				Ext.util.CSS.createStyleSheet(temp, me.qrCssId);

			}

			// now inject QR as DIV HTML-markup
			var objQRCode = qrcode(me.typeNumber, me.qrErrorCorrectLevel);

			objQRCode.getDOMSubTree({

				typeNumber : me.typeNumber, // defines the number of blocks in the code. blocks = typeNumber * 4 + 17 blocks, see http://www.denso-wave.com/qrcode/vertable1-e.html
				errorCorrectLevel : me.qrErrorCorrectLevel, // defines the redundancy in the code. One of .L, .M, .Q, .H = most redundant
				nodeParent : me.body.dom, // ID of the DIV to contain the code
				code : me.textToEncode // text to be encoded

			});

			break;

		case 'canvas':

			//
			// render as HTML5 CANVAS element:
			//

			// inject a canvas & save reference
			me.qrCanvas = Ext.DomHelper.append(me.body.dom, {

					tag : 'canvas',
					id : me.id + '-qrcanv' // allows easy later selection for CSS rules etc

				});

			// setup new object
			var objQRCode = qrcode(me.typeNumber, me.qrErrorCorrectLevel);

			// now draw QR code on canvas
			objQRCode.drawOnCanvas({

				code : me.textToEncode, // code to be encoded
				divCanvas : me.qrCanvas, // where to draw
				dotSize : parseInt(me.qrBlocksize), // a block's dimension
				dotFillStyle : me.qrBackgroundColor, // CSS color for foreground
				divContainerAttributes : { // container DIV's HTML attributes, optional

					style : 'width:100%; height:100%; background-color:' + me.qrForegroundColor

				}

			});

			break;

		case 'gif':

			//
			// render as GIF element:
			//

			// create the code
			var objQRCode = qrcode(me.typeNumber, me.qrErrorCorrectLevel);
			objQRCode.addData(me.textToEncode);
			objQRCode.make();

			// create base64 encoded image markup
			var b64 = objQRCode.createImgTag(parseInt(me.qrBlocksize), 0);

			// add GIF image to panel
			me.add({
				xtype : 'image',
				src : b64.split('"')[1], // extract src url from '<img src="...">
				id : me.id + '-qrgif', // allows easy later selection for CSS rules etc
				width : '100%',
				height : '100%'
			});
			break;

		} // switch(me.qrRenderMethod) {

		// establish click hander, if not opted-out
		if (!me.qrNoClickHandler) {

			// display fullscreen QR code when clicked
			me.body.on('click', me.onQRClick, me);

		}; // if(! me.qrNoClickHandler)

	}, // afterRender : function () {

	// free ressources when destroyed
	onDestroy : function () {

		var me = this;

		me.callParent(arguments);

		// remove the now obsolete CSS rules, if any
		if (me.qrCssId) {

			Ext.util.CSS.removeStyleSheet(me.qrCssId);
			delete me.qrCssId;

		}

	}, // onDestroy : function () {

	// find minimum Type Number for a string or me.textToEncode
	getMinTypeNumber : function (text) {

		// use property if no text given
		if (!text)
			text = this.textToEncode;

		// currently available only for this level
		if (this.qrErrorCorrectLevel == 'L') {

			if (text.length < 18)
				return 1;

			if (text.length < 33)
				return 2;

			if (text.length < 54)
				return 3;

			if (text.length < 79)
				return 4;

			if (text.length < 107)
				return 5;

			if (text.length < 135)
				return 6;

			if (text.length < 155)
				return 7;

			if (text.length < 193)
				return 8;

			if (text.length < 231)
				return 9;

			if (text.length < 272)
				return 10;

		} // if(this.qrErrorCorrectLevel == 'L') {

		return -1; // = can't find out

	}, // getMinTypeNumber: function( text ) {

	// handle click into QRcode
	onQRClick : function () {

		var me = this,
		vpSize = Ext.getDoc().getViewSize(), // = browser window size
		smallerDimension = (vpSize.height < vpSize.width ? vpSize.height : vpSize.width),
		posOld = me.el.getPageBox(); // = current QR code position & size

		// create white full screen mask showing only a big QR
		// opacity is set later by fadeIn()
		// mask size is VP size, not document size!
		var mask = Ext.create('Ext.container.Container', {

				floating : true,
				shadow : false,
				style : 'border:none; background-color:#fff; opacity:0;',
				width : vpSize.width,
				height : vpSize.height

			});

		// this is the QR code that maximizes
		var qrFullscreen = Ext.create('Ext.ux.QrPanel', {

				shadow : false,
				preventHeader : true,
				textToEncode : me.textToEncode,
				typeNumber : me.typeNumber,
				qrRenderMethod : (Ext.supports.Canvas ? 'canvas' : me.qrRenderMethod),
				qrErrorCorrectLevel : me.qrErrorCorrectLevel,
				qrBlocksize : Math.floor(smallerDimension / (me.qrModuleCount + 2)),
				qrNoClickHandler : true

			});

		// place it on small version
		qrFullscreen.setSize({
			width : posOld.width,
			height : posOld.height
		});

		qrFullscreen.setPosition(posOld.left, posOld.top);

		// add QR and show it
		mask.add(qrFullscreen);
		mask.show();

		// use 0.5 second transitions
		var duration = 500; 

		// reusable fullscreen terminator function
		var terminate = function () {

			// have a smooth end too
			qrFullscreen.animate({
				duration : duration,
				to : {
					opacity : 0,
					width : posOld.width,
					height : posOld.height,
					top : posOld.top,
					left : posOld.left
				}
			});

			// unmask old content
			mask.el.fadeOut({

				duration : duration,
				callback : function () {
					// remove mask from DOM tree after fade
					mask.destroy();

				}

			});

		}; // var terminate = function() {

		// calc left position of the final qr
		var posleft = (vpSize.width - qrFullscreen.qrBlocksize * me.qrModuleCount) / 2;

		// we like it smooth...
		mask.el.fadeIn({
			duration : duration,
			callback : function () {

				// click || keypress = end fullscreen display
				mask.focus();
				mask.mon(mask.el, 'click', terminate);
				mask.mon(Ext.getDoc(), 'keypress', terminate);

				// show readable textToEncode?
				if (me.qrDisplayTextWhenFullscreen) {

					// CSS in all cases
					var style = 'position:absolute; text-align:center; overflow:hidden; opacity:0; ';

					// display textToEncode left or below?
					if (smallerDimension == vpSize.height) {
						// display to the left
						style += 'top:45%; left:20px; width:' + (posleft - 30) + 'px; margin:auto;';
					} else {
						// display below
						style += 'top:' + ((qr.qrBlocksize + 2) * me.qrModuleCount) + 'px; left:5%; width:90%;';

					}

					// create text
					var text = Ext.String.htmlEncode(me.textToEncode);

					// turn URLs into clickable links
					if (me.textToEncode.substr(0, 7) == 'http://')
						text = '<a target="_blank" href="' + me.textToEncode + '">' + text + '</a>';

					// insert text
					mask.el.appendChild(Ext.DomHelper.createDom({
							tag : 'div',
							style : style,
							html : text
						})).fadeIn();

				} // if( me.qrDisplayTextWhenFullscreen ) {

			} // callback : function ()

		}); // mask.el.fadeIn({

		// animate QR growth
		qrFullscreen.animate({
			duration : duration,
			to : {
				opacity : 1,
				width : qrFullscreen.qrBlocksize * me.qrModuleCount,
				height : qrFullscreen.qrBlocksize * me.qrModuleCount,
				top : qrFullscreen.qrBlocksize,
				left : posleft
			}
		});

	} // onClick: function() {

}); // Ext.define('Ext.ux.QrPanel', {
